/*
 * Copyright (C) 2013 Buptcoder
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.buptcoder.wifimap;

import java.util.List;

import com.buptcoder.wifimap.Utils.Constants;
import com.buptcoder.wifimap.Utils.MapLog;
import com.buptcoder.wifimap.Utils.Utils;
import com.buptcoder.wifimap.location.Locator;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;

import android.os.Handler;
import android.os.IBinder;
import android.os.Message;

public class WifiScanService extends Service {
    private static final String TAG = "WiFiScanService";

    private static final long UPDATE_INTERVAL_TIME = 1000 * 60 * 3;
    private static final int START_SAVE_THREAD = 1;
    private static final int COMPLETE_SAVE_THREAD = 2;
    /**
     * These values are matched in string arrays -- changes must be kept in sync
     */
    private static final int SECURITY_NONE = 0;
    private static final int SECURITY_WEP = 1;
    private static final int SECURITY_PSK = 2;
    private static final int SECURITY_EAP = 3;

    private WifiManager mWifiManager;
    private IntentFilter mFilter;
    private BroadcastReceiver mReceiver;
    private Context mContext;
    private Scanner mScanner;

    private Handler mUiHandler;
    private boolean isSaving = false;
    private Locator mLocator;

    private ContentResolver mResolver;
    private long mLastUpateTime;

    enum QueryResult {
        New, Update, Existing
    };

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        mContext = this;
        mResolver = mContext.getContentResolver();
        mScanner = new Scanner();
        initiateReceiver();
        initiateHandler();
        registerReceiver(mReceiver, mFilter);
        mLocator = Locator.getInstance(this.getApplicationContext());
    }

    private void initiateReceiver() {
        mFilter = new IntentFilter();
        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);

        mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                handleEvent(intent);
            }
        };
    }

    private void handleEvent(Intent intent) {
        String action = intent.getAction();
        if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
            MapLog.d(TAG, "Scan Reseult avaible received!");
            if (isTimeToUpdate())
                mUiHandler.sendEmptyMessage(START_SAVE_THREAD);
        }
    }

    private boolean isTimeToUpdate() {
        long currentTime = System.currentTimeMillis();
        boolean result = false;
        if (mLastUpateTime == -1) {
            mLastUpateTime = currentTime;
            result = true;
        } else {
            if (currentTime - mLastUpateTime > UPDATE_INTERVAL_TIME) {
                mLastUpateTime = currentTime;
                result = true;
            }
        }
        return result;
    }

    @SuppressLint("HandlerLeak")
    private void initiateHandler() {
        mUiHandler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                if (message.what == START_SAVE_THREAD && !isSaving) {
                    startSaveThread();
                    isSaving = true;
                } else if (message.what == COMPLETE_SAVE_THREAD) {
                    isSaving = false;
                }
            }
        };
    }

    private void startSaveThread() {
        MapLog.begin(TAG);
        SaveAPThread thread = new SaveAPThread();
        thread.run();
    }

    private void handleScannedWifiInfo() {
        List<ScanResult> results = mWifiManager.getScanResults();
        if (results != null) {
            for (ScanResult result : results) {
                // Ignore hidden and ad-hoc networks.
                if (result.SSID == null || result.SSID.length() == 0
                        || result.capabilities.contains("[IBSS]")) {
                    continue;
                }

                if (result.frequency == 0) {
                    continue;
                }
                QueryResult queryResult = checkWifiAPInfo(Utils
                        .removeDoubleQuotes(result.SSID));
                handleWifiInfoWithResult(queryResult, result);
            }
        }
    }

    private QueryResult checkWifiAPInfo(String ssid) {
        QueryResult result = QueryResult.New;
        Cursor cursor = null;
        try {
            cursor = mResolver.query(Constants.WIFI_APINFO_URL,
                    Constants.getWifiProjection(), "ssid=?",
                    new String[] { ssid }, null);
            if (cursor != null && cursor.getCount() > 0) {
                result = QueryResult.Update;
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (cursor != null) {
                cursor.close();
            }
        }
        return result;
    }

    private void handleWifiInfoWithResult(QueryResult queryResult,
            ScanResult result) {
        if (queryResult == QueryResult.New) {
            saveAPInfoToDB(result);
        } else if (queryResult == QueryResult.Update) {
            updateAPInfoinDB(result);
        }
    }

    private void saveAPInfoToDB(ScanResult result) {
        ContentValues values = new ContentValues();
        values.put(Constants.SSID, Utils.removeDoubleQuotes(result.SSID));
        values.put(Constants.LAT, mLocator.getLatitude());
        values.put(Constants.LON, mLocator.getLongitude());
        values.put(Constants.UPDATE, System.currentTimeMillis());
        values.put(Constants.TYPE, getSecurity(result));
        try {
            Uri insertResult = mResolver.insert(Constants.WIFI_APINFO_URL,
                    values);
            if (insertResult == null) {
                MapLog.d(TAG, "insert Eroor");
            }
        } catch (SQLiteException e) {
            e.printStackTrace();
            MapLog.d(TAG, "insert Eroor");

        }
    }

    private static int getSecurity(ScanResult result) {
        if (result.capabilities.contains("WEP")) {
            return SECURITY_WEP;
        } else if (result.capabilities.contains("PSK")) {
            return SECURITY_PSK;
        } else if (result.capabilities.contains("EAP")) {
            return SECURITY_EAP;
        }
        return SECURITY_NONE;
    }

    private void updateAPInfoinDB(ScanResult result) {
        String ssid = Utils.removeDoubleQuotes(result.SSID);
        ContentValues values = new ContentValues();
        values.put(Constants.LAT, mLocator.getLatitude());
        values.put(Constants.LON, mLocator.getLongitude());
        values.put(Constants.UPDATE, System.currentTimeMillis());
        values.put(Constants.TYPE, getSecurity(result));
        try {
            int updateResult = mResolver.update(Constants.WIFI_APINFO_URL,
                    values, "ssid=?", new String[] { ssid });
            if (updateResult <= 0) {
                MapLog.d(TAG, "update Eroor");
            }
        } catch (SQLiteException e) {
            MapLog.d(TAG, "update Eroor");

            e.printStackTrace();
        }

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        mLastUpateTime = -1;
        mScanner.start();
        return Service.START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (mScanner != null) {
            mScanner.stop();
        }
        if (mReceiver != null) {
            unregisterReceiver(mReceiver);
        }
        if (mLocator != null) {
            mLocator = null;
        }
    }

    class SaveAPThread extends Thread {
        @Override
        public void run() {
            handleScannedWifiInfo();
        }
    }

    @SuppressLint("HandlerLeak")
    private class Scanner extends Handler {
        private int mRetry = 0;

        void start() {
            if (!hasMessages(0)) {
                sendEmptyMessage(0);
            }
        }

        void stop() {
            mRetry = 0;
            removeMessages(0);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mWifiManager.startScan()) {
                mRetry = 0;
            } else if (++mRetry >= 3) {
                mRetry = 0;
                return;
            }
            sendEmptyMessageDelayed(0, 1000 * 60 * 2);
        }
    }
}
